package obj;
import util.Globals;


public class Organization implements Comparable {
//	public class Organization implements Comparable<Organization> {

	/** data members */
	private int index;
	private double sensitivity;
	private int[] trait; // strategy trait vector (array for faster computation)
	private double strategy;
//	private double error; // do we really need to make this explicit?
	private double performance;
	private double aspiration;
	private int decision; // 0 for "not set"; 1 for "keep", 2 for "change"
	private int refGroupSize = 0;
	private int potentialRefGroupSize = 0;
	private String refGroupIdx = "";
	private String potentialRefGroupIdx = "";
	private String imitation;
	private int[] referenceTraits;
	private String action = "";
//	private int[] bestPerformerTraits;
//	private String[] dominantTraitsString; // string representation of dominant (of better performers) traits
	
	// Constructor
    /**
     * 
     */
	public Organization(double s, String imitate) {
		Globals.maxOrgIdx++; // increment maxOrgIndex
//		index = i;
		index = Globals.maxOrgIdx;
		sensitivity = s;
		imitation = imitate;
		// set random strategy trait
		trait = new int[Globals.numTraits];
		for (int t = 0; t < Globals.numTraits; t++) {
			trait[t] = Globals.rand.nextInt(Globals.numTraitValues); 
		}
		// set strategy / performance
		strategy = Globals.rand.nextGaussian();
		performance = strategy + (Globals.uncertainty * Globals.rand.nextGaussian()); // p = s + u*e
		// set initial aspiration? (random as per Greve)
		aspiration = Globals.rand.nextGaussian();
		plan(); // sets decision to 1 or 2 based on asp vs. perf
//		decision = 0; // initialize to 0 (not set)
	}

	// Accessors
	public int getIndex() {
		return index;
	}
	
	public double getSensitivity() {
		return sensitivity;
	}
	
	public String getImitationStrategy() {
		return imitation;
	}
	
	public int[] getTrait() {
		return trait;
	}
	
	// an organization's current strategy
	public double getStrategy() {
		return strategy;
	}
	
	// organization's current performance
	public double getPerformance() {
		return performance;
	}
	
	// return aspiration level
	public double getAspiration() {
		return aspiration;
	}
	
	public int getRefGroupSize() {
		return refGroupSize;
	}
	
	// sets number of orgs in reference group
	public void setRefGroupSize(int numOrgs) {
		refGroupSize = numOrgs;
	}
	
	public void setPotentialRefGroupSize(int numOrgs) {
		potentialRefGroupSize = numOrgs;
	}
	
	public void setRefGroupIdx(String idxString) {
		refGroupIdx = idxString;
	}
	
	public void setPotentialRefGroupIdx(String idxString) {
		potentialRefGroupIdx = idxString;
	}
	
	public void setAspiration(double asp) {
		aspiration = asp;
	}
	
//	public void setBestPerformerTraits(int[] bestTraits) {
//		System.arraycopy(bestTraits, 0, bestPerformerTraits, 0, bestTraits.length);
//	}
//	
//	public void setDominantTraitString(String[] dominantTraits) {
//		System.arraycopy(dominantTraits, 0, dominantTraitsString, 0, dominantTraitsString.length);
//	}
	
	/** Commands */
	// Plan next period move (i.e., make decision to change strategy or keep current strategy)
	// decision -> 0 for "not set", 1 for "keep", 2 for "change"
	public void plan() {
		// probDraw (rand) <= probChange(perf, asp)
		// asp was set by market.plan; performance is t-1 perf
		if (Globals.rand.nextDouble() <= probChange(performance, aspiration)) {  // new strategy
			decision = 2; // change
		} else {
			decision = 1; // keep
		}
	}
	
	public String getDecision() {
		if (decision == 1) { return "keep"; }
		else if (decision == 2) { return "change"; }
		else if (decision == 0) { return "error(0)"; }
		else { return "error(unknown)"; }
	}
	
	// Execute next current plan => realize new performance
	public void execute() {
		action = "";
		// let's set aspiration to past performance before we realize a new performance 
		// so that if ref group is null then not setting the aspiration would keep 
		// immediate past performance as aspiration
		aspiration = performance; // aspiration is set to immediate past performance
		if (Globals.debug) { System.out.println("executing decision (" + decision + ")"); }
		if (decision == 0) { // error
			System.err.println("Error: decision has not been set.");
			System.exit(0);
		} else if (decision == 1) { // keep
			// generate new performance with old strategy
			performance = strategy + (Globals.uncertainty * Globals.rand.nextGaussian()); // p = s + u*e
		} else if (decision == 2) {
			// generate new performance with new strategy
			strategy = Globals.rand.nextGaussian();
			performance = strategy + (Globals.uncertainty * Globals.rand.nextGaussian()); // p = s + u*e
			// mutate (i.e., change traits)
			mutate();
			
		} else { // not 0, 1, or 2 => error; this should NEVER happen
			System.err.println("Error: unknown decision.");
			System.err.println("Decision should be 0 (not set), 1 (keep) or 2 (change);");
			System.exit(0);
		}
		// reset decision to 0 (not set) for next round
		decision = 0;
	}
	
	// return distance (similarity) with another org -- 0 (completely dissimilar) to 1 (completely same)
	public double getSimilarity(Organization other) {
		int[] otherOrgTrait = other.getTrait();
		int same = 0; 
		for (int i = 0; i < Globals.numTraits; i++) {
			if (trait[i] == otherOrgTrait[i]) { same++; }
		}
//		double similarity = (double)same / (double)Globals.numTraits; 
//		return similarity;
		return ((double)same / (double)Globals.numTraits);
	}
	
	// calculate probability of strategy change as a function of performance vs. aspiration
	private double probChange(double perf, double asp) {
		double prob, logit;
		if (perf > asp) { logit = -2.0d - (perf - asp); } 
		else { logit = -2.0d - 0.25d * (perf - asp); }
		prob = Math.exp(logit) / (1.0d + Math.exp(logit));
		return prob;
	}
	
//	public int compareTo(Organization o) {
//		if (performance > o.getPerformance()) { return -1; } 
//		else if (performance == o.getPerformance()) { return 0; } 
//		else { return 1; }
//	}

	public int compareTo(Object o) {
		if (performance > ((Organization)o).getPerformance()) { return -1; } 
		else if (performance == ((Organization)o).getPerformance()) { return 0; } 
		else { return 1; }
	}

	private void mutate() {
		if (Globals.debug) { System.out.println("mutate start: org(" + index + ")"); }
		// what if refGroupSize = 0?  => for now even if imitation = best or dominant, the org will randomly mutate
		// what if org is the best performer within reference group when imitation = best?, for now we'll randomly mutate
		String imitate = imitation;
		int bestIdx = index;
		double maxPerf = performance;
		if (Globals.debug) {
			System.out.println("refGroupSize: " + refGroupSize);
		}
		if (refGroupSize == 0) {
			imitate = "random"; // CHANGE THIS TO IMITATION RULE IN CASE REFGROUP IS EMPTY
			action = "[" + imitation + "] RANDOM (refGroupSize = 0)";
		} else {
			if (Globals.debug) {
				System.out.println("RefGroupIdx: [" + refGroupIdx + "]");
			}
			if (imitation.equals("best")) {
				String[] indicesString = refGroupIdx.split(",");
				for (int i = 0; i < indicesString.length; i++) {
					int idx = Integer.parseInt(indicesString[i]);
					Organization other = app.Simulation.mkt.getOrg(idx);
					if (other == null) {
						// org "other" was probably replaced; do nothing
					} else {
//						double otherPerf = app.Simulation.mkt.getOrg(idx).getPerformance();
						double otherPerf = other.getPerformance();
						if (maxPerf < otherPerf) {
							bestIdx = idx;
							maxPerf = otherPerf;
						}
					}
				}
				if (bestIdx == index) { // If org is still the best
					imitate = "random";
					action = "[" + imitation + "] RANDOM (org is best performer among ref group)";
				}
			}
		}
		if (Globals.debug) { System.out.println("determining imitation action"); }
		
		
		if (imitate.equals("random")) {
			if (Globals.debug) { System.out.println("imitate action = random"); }
			// randomly determine trait to change and randomly mutate
			int[] traitToChange = generateRandomNumbers(Globals.numTraitsToMutate, Globals.numTraits);
			for (int i = 0; i < Globals.numTraitsToMutate; i++) {
				int currentValue = trait[traitToChange[i]];
				trait[traitToChange[i]] = generateRandomExcept(currentValue);
				if (imitation.equals("random")) {
					action = "[" + imitation + "] RANDOM (imitation = random)";
				} 

			}
		} else if (imitate.equals("best")) {
			if (Globals.debug) { System.out.println("imitate action = best"); }

			// find best neighbor (i.e., highest performance); if there is a draw, then randomly pick one
			// switch to best neighbor's trait if own is not the same.  If the whole trait vector is the same, do nothing
			Organization bestPerformer = app.Simulation.mkt.getOrg(bestIdx);
			int bestTrait[] = new int[Globals.numTraits];
			System.arraycopy(bestPerformer.getTrait(), 0, bestTrait, 0, Globals.numTraits);
			
			String diffTraitIdx = "";
			for (int i = 0; i < trait.length; i++) {
				if (trait[i] != bestTrait[i]) {
					diffTraitIdx += i + ",";
				}
			}
			/* random mutation */
			if (diffTraitIdx.equals("")) { // org's trait is the same as the best performer ==> random mutate
				int[] traitToChange = generateRandomNumbers(Globals.numTraitsToMutate, Globals.numTraits);
				for (int i = 0; i < Globals.numTraitsToMutate; i++) {
					int currentValue = trait[traitToChange[i]];
					trait[traitToChange[i]] = generateRandomExcept(currentValue);
				}
				action = "[" + imitation + "] RANDOM (org trait = best performer trait)";
			} else {
				String[] diffIndicesString = diffTraitIdx.split(",");
				int r = Globals.rand.nextInt(diffIndicesString.length);
				action = "[" + imitation + "] BEST (change nt:" + r + " from " + trait[r] + " to " + bestTrait[r];
				trait[r] = bestTrait[r];
			}
		} else if (imitate.equals("dominant")) {
			if (Globals.debug) { System.out.println("imitate action = dominant"); }

			// find the dominant traitvalue within all better performing neighbors (=> conceptual "dominant neighbor")
			// switch to the dominant neighbor's trait if own is not the same.  If the whole trait vector is the same, do nothing.
			String[] indicesString = refGroupIdx.split(",");
			if (Globals.debug) { System.out.println("refGroupIdx: " + refGroupIdx); }
			int[][] dominantTraits = new int[Globals.numTraits][Globals.numTraitValues];
//			double myPerformance = o.getPerformance();

			if (Globals.debug) { System.out.println("indicesString.length: " + indicesString.length); }
			if (Globals.debug) { System.out.println("indicesString: " + indicesString[0]); }
			for (int i = 0; i < indicesString.length; i++) {
				if (Globals.debug) {
					System.out.println("Number format exception: idx = " + indicesString[i]);
				}
				int idx = Integer.parseInt(indicesString[i]);
				if (Globals.debug) { System.out.println("idx: " + idx); }
				Organization other = app.Simulation.mkt.getOrg(idx);
				if (other == null) {
					// org "other" was replaced; do nothing
				} else {
//					if (Globals.debug) { System.out.println("other idx: " + other.getIndex()); }
					double otherPerf = other.getPerformance();
					if (otherPerf > performance) {
						int[] refTrait = other.getTrait();
						for (int t = 0; t < Globals.numTraits; t++) {
							dominantTraits[t][refTrait[t]]++; 
						}
					}
				}
				if (other == null) { System.out.println("PROBABLY THROW NULL POINTER EXCEPTION NOW"); }
			} // dominantTraits[][] is now set
			
		
			if (Globals.debug) { System.out.println("finding dominantTraits done"); }
			/*HERE*/
			
			String[] dominantTraitsString = new String[Globals.numTraits];
			for (int nt = 0; nt < Globals.numTraits; nt++) { dominantTraitsString[nt] = ""; } // initialize string
			for (int nt = 0; nt < Globals.numTraits; nt++) {
				int max = -1;
				for (int ntv = 0; ntv < Globals.numTraitValues; ntv++) {
					if (dominantTraits[nt][ntv] > max) {
						if (dominantTraits[nt][ntv] == trait[nt]) {
							dominantTraitsString[nt] = "";
						} else {
							dominantTraitsString[nt] = ntv + ",";
						}
						max = dominantTraits[nt][ntv];
					} else if (dominantTraits[nt][ntv] == max) {
						if (dominantTraits[nt][ntv] != trait[nt]) {
							dominantTraitsString[nt] += ntv + ",";
						}
					}	
				}
				max = -1;
			} // dominantTraitsString[] now only has other traits (, separated) that I can mutate to
			if (Globals.debug) { System.out.println("finding dominant trait string[] done"); }

			// first let's check if dominantTraitsString is empty (i.e., when org's traits == dominant trait => random mutation
			boolean empty = true;
			for (int i = 0; i < dominantTraitsString.length; i++) {
				if (!dominantTraitsString[i].equals("")) {
					empty = false;
					break;
				}
			}
			if (Globals.debug) { System.out.println("empty = " + empty); }
			
			if (Globals.debug) {
				System.out.print("dominantTraitsString: [");
				for (int i = 0; i < dominantTraitsString.length; i++) {
					System.out.print(dominantTraitsString[i] + ":");
				}
				System.out.println("]");
			}
			
			if (empty) { // empty so random mutation
				int[] traitToChange = generateRandomNumbers(Globals.numTraitsToMutate, Globals.numTraits);
				for (int i = 0; i < Globals.numTraitsToMutate; i++) {
					int currentValue = trait[traitToChange[i]];
					trait[traitToChange[i]] = generateRandomExcept(currentValue);
					action = "[" + imitation + "] RANDOM (org trait = dominant trait)";

				}
			} else { // not empty so pick one randomly
				boolean found = false;
				while (!found) {
					int r = Globals.rand.nextInt(Globals.numTraits);
					if (!dominantTraitsString[r].equals("")) { // dominantTraitsString at r is not empty so pick one of those
						String[] result = dominantTraitsString[r].split(",");
						int rr = Globals.rand.nextInt(result.length); // randomly choose if multiple good traits
						
						if (Globals.debug) {
							System.out.println("changing nt(" + r + " from " + trait[r] + " to " + result[rr]);
						}
						
						action = "[" + imitation + "] DOMINANT (change nt:" + r + " from " + trait[r] + " to " + result[rr];
						trait[r] = Integer.parseInt(result[rr]);
						found = true;
					}
				}
			}
			
		} else { // impossible
			System.err.println("imitation must be either random, best or dominant.  Check configuration file.");
			System.exit(0);
		}
		
	}
	
	
	private static int[] generateRandomNumbers(int count, int range) {
		if (count > range) {
			System.err.println("ERROR: numTraitToMutate cannot be greater than numTraits");
			System.exit(0);
		}
		int[] returnArray = new int[count];
		for (int i = 0; i < count; i++) { returnArray[i] = -1; } // initialize with -1s since 0 may be a random number
		int todo = 0;
		boolean found = false;
		while (todo < count) {
			int r = Globals.rand.nextInt(range);
			for (int i = 0; i < count; i++) { // loop through to see if I already have this
				if (returnArray[i] == r) {
					found = true;
				}
			}
			if (!found) {
				for (int j = 0; j < count; j++) {
					if (returnArray[j] == -1) {
						returnArray[j] = r;
						todo++;
						break;
					}
				}
			}
			found = false;
		}
		return returnArray;
	}

	private static int generateRandomExcept(int toExclude) {
		int retInt = -1;
		if (toExclude > Globals.numTraitValues) {
			System.err.println("ERROR: toExclude cannot be greater than numTraitValues");
			System.exit(0);
		}
		boolean found = false;
		while (!found) {
			int r = Globals.rand.nextInt(Globals.numTraitValues);
			if (r != toExclude) {
				retInt = r;
				found = true;
			}
		}
		return retInt;
	}
	
	public String toString() {
		String retString = index + "\t" + strategy + "\t" + performance + "\t" + refGroupSize + "\t" + potentialRefGroupIdx + "\t" + refGroupIdx + "\t" + getDecision() + "\t[";
		for (int i = 0; i < trait.length; i++) {
			if (i < trait.length - 1) {
				retString += trait[i] + ", ";
			} else {
				retString += trait[i] + "";
			}
		}
		retString += "]\t";
		retString += action;
		action = ""; // reset action?
			
		return retString;
	}
}